import { MessageHandler, ProcessResult, Logger } from '@/types';
import { Message, MessageProcessResult } from './message';
import { createLogger } from '@/utils/logger';
import { z } from 'zod';

/**
 * Abstract base class for message handlers
 */
export abstract class BaseMessageHandler implements MessageHandler {
  protected readonly logger: Logger;

  constructor(logger?: Logger) {
    this.logger = logger ?? createLogger(this.constructor.name);
  }

  /**
   * Handle the message - must be implemented by subclasses
   */
  abstract handle(message: Message): Promise<ProcessResult>;

  /**
   * Handle errors during message processing
   */
  async onError(message: Message, error: Error): Promise<ProcessResult> {
    this.logger.error(`Error processing message ${message.id}`, error);
    return MessageProcessResult.failure(error.message, this.shouldRetry(message, error));
  }

  /**
   * Determine if message should be retried
   */
  shouldRetry(message: Message, _error: Error): boolean {
    // Default retry logic: retry up to 3 times
    return message.retryCount < 3;
  }
}

/**
 * Function-based message handler
 */
export class FunctionHandler extends BaseMessageHandler {
  private readonly handlerFunction: (body: unknown) => Promise<unknown> | unknown;
  private readonly errorHandler?: (message: Message, error: Error) => Promise<ProcessResult>;

  constructor(
    handlerFunction: (body: unknown) => Promise<unknown> | unknown,
    errorHandler?: (message: Message, error: Error) => Promise<ProcessResult>,
    logger?: Logger
  ) {
    super(logger);
    this.handlerFunction = handlerFunction;
    this.errorHandler = errorHandler || undefined;
  }

  async handle(message: Message): Promise<ProcessResult> {
    try {
      const result = await this.handlerFunction(message.body);
      return MessageProcessResult.success(result);
    } catch (error) {
      if (this.errorHandler) {
        return await this.errorHandler(message, error as Error);
      }
      return await this.onError(message, error as Error);
    }
  }
}

/**
 * Batch message handler for processing multiple messages together
 */
export class BatchHandler extends BaseMessageHandler {
  private readonly batchSize: number;
  private readonly timeoutMs: number;
  private readonly batch: Message[] = [];
  private lastBatchTime: number = Date.now();
  private batchTimer?: NodeJS.Timeout;

  constructor(batchSize = 10, timeoutMs = 30000, logger?: Logger) {
    super(logger);
    this.batchSize = batchSize;
    this.timeoutMs = timeoutMs;
  }

  async handle(message: Message): Promise<ProcessResult> {
    this.batch.push(message);

    // Check if we should process the batch
    if (this.batch.length >= this.batchSize || this.shouldProcessBatch()) {
      return await this.processBatch();
    }

    // Set timer for batch processing if not already set
    if (!this.batchTimer) {
      this.batchTimer = setTimeout(() => {
        this.processBatch().catch(error => {
          this.logger.error('Error processing batch', error);
        });
      }, this.timeoutMs);
    }

    return MessageProcessResult.success(undefined, 'Added to batch');
  }

  private shouldProcessBatch(): boolean {
    return Date.now() - this.lastBatchTime > this.timeoutMs;
  }

  private async processBatch(): Promise<ProcessResult> {
    if (this.batch.length === 0) {
      return MessageProcessResult.success(undefined, 'Empty batch');
    }

    const currentBatch = [...this.batch];
    this.batch.length = 0; // Clear the batch
    this.lastBatchTime = Date.now();

    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
      this.batchTimer = undefined;
    }

    try {
      const result = await this.processBatchMessages(currentBatch);
      this.logger.info(`Processed batch of ${currentBatch.length} messages`);
      return result;
    } catch (error) {
      this.logger.error(
        `Error processing batch of ${currentBatch.length} messages`,
        error as Error
      );
      return MessageProcessResult.failure((error as Error).message);
    }
  }

  /**
   * Process a batch of messages - must be implemented by subclasses
   */
  protected async processBatchMessages(messages: Message[]): Promise<ProcessResult> {
    // Default implementation processes messages individually
    const results: ProcessResult[] = [];

    for (const message of messages) {
      try {
        const result = await this.processSingleMessage(message);
        results.push(result);
      } catch (error) {
        results.push(MessageProcessResult.failure((error as Error).message));
      }
    }

    const successCount = results.filter(r => r.success).length;
    const failureCount = results.length - successCount;

    if (failureCount === 0) {
      return MessageProcessResult.success(
        { processed: successCount },
        `Successfully processed ${successCount} messages`
      );
    } else {
      return MessageProcessResult.failure(
        `Processed ${successCount} messages, ${failureCount} failed`
      );
    }
  }

  /**
   * Process a single message within a batch - can be overridden by subclasses
   */
  protected async processSingleMessage(_message: Message): Promise<ProcessResult> {
    // Default implementation - subclasses should override this
    return MessageProcessResult.success(undefined, 'Processed in batch');
  }
}

/**
 * Type-safe message handler using Zod schemas
 */
export class TypedHandler<T> extends BaseMessageHandler {
  private readonly schema: z.ZodSchema<T>;
  private readonly processor: (data: T) => Promise<unknown> | unknown;

  constructor(
    schema: z.ZodSchema<T>,
    processor: (data: T) => Promise<unknown> | unknown,
    logger?: Logger
  ) {
    super(logger);
    this.schema = schema;
    this.processor = processor;
  }

  async handle(message: Message): Promise<ProcessResult> {
    try {
      // Validate message body against schema
      const validatedData = this.schema.parse(message.body);

      // Process the validated data
      const result = await this.processor(validatedData);

      return MessageProcessResult.success(result);
    } catch (error) {
      if (error instanceof z.ZodError) {
        const validationErrors = error.issues
          .map(issue => `${issue.path.join('.')}: ${issue.message}`)
          .join(', ');

        this.logger.warn(`Validation failed for message ${message.id}: ${validationErrors}`);
        return MessageProcessResult.failure(`Validation error: ${validationErrors}`, false);
      }

      return await this.onError(message, error as Error);
    }
  }
}

/**
 * Conditional message handler that routes messages based on conditions
 */
export class ConditionalHandler extends BaseMessageHandler {
  private readonly conditions: Array<{
    condition: (message: Message) => boolean;
    handler: MessageHandler;
  }> = [];
  private defaultHandler?: MessageHandler;

  constructor(logger?: Logger) {
    super(logger);
  }

  /**
   * Add a conditional handler
   */
  when(condition: (message: Message) => boolean, handler: MessageHandler): ConditionalHandler {
    this.conditions.push({ condition, handler });
    return this;
  }

  /**
   * Set default handler for messages that don't match any condition
   */
  otherwise(handler: MessageHandler): ConditionalHandler {
    this.defaultHandler = handler;
    return this;
  }

  async handle(message: Message): Promise<ProcessResult> {
    // Find the first matching condition
    for (const { condition, handler } of this.conditions) {
      if (condition(message)) {
        return await handler.handle(message);
      }
    }

    // Use default handler if no condition matches
    if (this.defaultHandler) {
      return await this.defaultHandler.handle(message);
    }

    return MessageProcessResult.failure('No handler found for message');
  }
}

/**
 * Retry handler that wraps another handler with retry logic
 */
export class RetryHandler extends BaseMessageHandler {
  private readonly baseHandler: MessageHandler;
  private readonly maxRetries: number;
  private readonly retryDelay: number;
  private readonly backoffFactor: number;

  constructor(
    baseHandler: MessageHandler,
    maxRetries = 3,
    retryDelay = 1000,
    backoffFactor = 2,
    logger?: Logger
  ) {
    super(logger);
    this.baseHandler = baseHandler;
    this.maxRetries = maxRetries;
    this.retryDelay = retryDelay;
    this.backoffFactor = backoffFactor;
  }

  async handle(message: Message): Promise<ProcessResult> {
    let lastError: Error | undefined;

    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        const result = await this.baseHandler.handle(message);

        if (result.success) {
          if (attempt > 0) {
            this.logger.info(`Message ${message.id} succeeded after ${attempt} retries`);
          }
          return result;
        }

        if (!result.shouldRetry || attempt === this.maxRetries) {
          return result;
        }

        // Wait before retry
        const delay = this.retryDelay * Math.pow(this.backoffFactor, attempt);
        await this.sleep(delay);

        this.logger.info(
          `Retrying message ${message.id}, attempt ${attempt + 1}/${this.maxRetries}`
        );
      } catch (error) {
        lastError = error as Error;

        if (attempt === this.maxRetries) {
          break;
        }

        // Wait before retry
        const delay = this.retryDelay * Math.pow(this.backoffFactor, attempt);
        await this.sleep(delay);

        this.logger.info(
          `Retrying message ${message.id} after error, attempt ${attempt + 1}/${this.maxRetries}`
        );
      }
    }

    return MessageProcessResult.failure(lastError?.message || 'Max retries exceeded', false);
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

/**
 * Utility functions for creating handlers
 */
export const createFunctionHandler = (
  handlerFunction: (body: unknown) => Promise<unknown> | unknown,
  errorHandler?: (message: Message, error: Error) => Promise<ProcessResult>
): FunctionHandler => {
  return new FunctionHandler(handlerFunction, errorHandler);
};

export const createTypedHandler = <T>(
  schema: z.ZodSchema<T>,
  processor: (data: T) => Promise<unknown> | unknown
): TypedHandler<T> => {
  return new TypedHandler(schema, processor);
};

export const createBatchHandler = (batchSize = 10, timeoutMs = 30000): BatchHandler => {
  return new BatchHandler(batchSize, timeoutMs);
};
